選択範囲をMarkdown記法に変換してclip boardにcopyするPopupMenu
選択範囲をmarkdown記法に変換してclipboardにcopyするPopupMenuを作ってみたい
2020-10-16 08:57:37 できた
アイコン記法は無視している
動機
/icons/Scrapbox.iconに書いた文章をレポートに書き写すことがよくある
それを簡単にできるようにしたい
あとグループでの共同編集をやりやすくする
Wordの共同編集は重いので、3 ~ 4人で共同編集しようとすると固まったりエラーが出たり場合によってはデータが飛ぶことがある
代わりにreal time編集に優れた/icons/Scrapbox.iconで文章を書き、それをwordなりmarkdownなりに変換&提出すればレポート作成がとても楽になる
実装
UserScriptで使えるscrapbox-parserを使って構文解析する
userは事前に自分のページで↑を読み込んでもらう
見出し
トップレベルのみ
見出し以外は無視
アイコン記法は全て無視する
/icons/doing.iconコードブロックの言語名
言語名と拡張子との対応リストを別に作っておいたほうがいいな
switch文中に書いたりすると、読みにくくなる
こんな感じ
code:data.json
[
{
"extensions":"javascript","js",
"filetype": "javascript"
},
]
2020-11-05 10:00:11 少しだけ実装した
選択範囲をMarkdown記法に変換してclip boardにcopyするPopupMenu#5f82fd071280f00000e0ae36
やりたい
/icons/doing.icon相対的にindentを変える
例えば選択範囲の開始がインデントを1つ含んでいた場合は、それ以降1 indentをtop indentとして扱う
より少ないインデントの行を選択範囲中に含んでいた場合は、そちらを基準にする
この辺はLETUS online editor形式に変換してcopyするPopupMenuで実装した
それをこっちにscriptに移植すればいいだろう。
2020-11-12 01:45:28 codeだけ書いた
テストはしていない
refactoring
TypeScriptにする
arrow functionにする
テストを書く
/icons/hr.icon
似たようなUserScriptは既にある
/shokai/Scrapboxからmarkdownに変換
line.section.startを見出し変換につかっている
よさそうtakker.icon
/daiiz/ScrapboxコンテンツをMarkdownに変換するBookmarklet
daiiz/sb2md: Scrapbox contents -> Markdown
bookmarkletにしてもいいかも?
takker.iconは選択範囲に適用できるようにしたい
2023-02-06
21:47:07 iconをfont awesomeにした
2022-09-25
15:57:14 /villagepump/2022/09/19#6327a68c5f1e0d000024ddbf
linkの時になぜかhttps://scrapbox.io が入ってしまう
直した
2022-05-20
17:47:39 /nishio/Scrapbox→Markdown変換#6278d82faff09e0000eca1f6
/icons/知らんかった.icon
試した
外部リンク記法は問題なく変換できる
https://xxx.comを変換すると[](https://xxx.com)にある
あーそういうことかtakker.icon
https://xxx.comのときは生のURLを直接貼ることにしよう
修正done
2022-05-15
17:50:02 変換部分だけJSDocを付けた
その他、deno fmtで整形した
コードが雑な点には手を入れていない
そのうち直したいっちゃ直したい
17:13:50 /villagepump/2022/05/15#6280b2abaff09e0000e586fe
$ curl https://scrapbox.io/api/code/takker/選択範囲をMarkdown記法に変換してclip_boardにcopyするPopupMenu/deno.json > deno.json
$ deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/takker/選択範囲をMarkdown記法に変換してclip_boardにcopyするPopupMenu/script.js
code:script.js
import { convertSb2Md } from "./convert.js";
import { ScrapboxParser } from "../scrapbox-parser.min.js/parser.js";
// markdown変換
scrapbox.PopupMenu.addButton({
title: "\uf60f",
onClick: (text) => {
(async () => {
try {
const blocks = ScrapboxParser.parse(text, { hasTitle: false });
//console.log('Parserd text:');
//console.log(blocks);
// このindent levelを基準にする
const topIndentLevel = Math.min(...blocks.map((block) => block.indent));
await navigator.clipboard.writeText(
blocks.map((block) => convertSb2Md(block, topIndentLevel)).join("\n"),
);
//console.log('Copied.');
} catch (e) {
alert(Failed to copy:\n${JSON.stringify(e)});
}
})();
},
});
あとはblocksをmarkdownに変換するコードを書く必要がある
まず、markdown記法をまとめる必要があるなtakker.icon
斜体
DenoでJavaScriptを型チェック対象にする
code:convert.js
// @ts-check
/**
* @typedef {import("../scrapbox-parser/mod.ts").Block} Block
* @typedef {import("../scrapbox-parser/mod.ts").Table} Table
* @typedef {import("../scrapbox-parser/mod.ts").Line} Line
* @typedef {import("../scrapbox-parser/mod.ts").Node} NodeType
* @typedef {import("../scrapbox-jp%2Ftypes/userscript.ts").Scrapbox} Scrapbox
*/
/** Scrapbox記法をMarkdown記法に変える
*
* @param {Block} block
* @param {number} topIndentLevel
* @return {string}
*/
export const convertSb2Md = (block, topIndentLevel) => {
switch (block.type) {
case "title":
return ""; // タイトルは選択範囲に入らないので無視
case "codeBlock":
return [
block.fileName,
\n\`\`\`${getFileType(block.fileName)},
block.content,
"\\`\\n",
].join("\n");
case "table":
return convertTable(block);
case "line":
return convertLine(block, topIndentLevel);
}
};
/** Table記法の変換
*
* @param {Table} table
* @return {string}
*/
const convertTable = (table) => {
const line = table.fileName;
// columnsの最大長を計算する
const maxCol = Math.max(...table.cells.map((row) => row.length));
table.cells.forEach((row, i) => {
line.push(
`| ${
row.map((column) => column.map((node) => convertNode(node)).join(""))
.join(" | ")
} |`,
);
if (i === 0) line.push(|${" -- |".repeat(maxCol)});
});
return line.join("\n");
};
const INDENT = " "; // インデントに使う文字
/** 行の変換
*
* @param {Line} line
* @param {number} topIndentLevel
* @return {string}
*/
const convertLine = (line, topIndentLevel) => {
const content = line.nodes
.map((node) =>
convertNode(node, { section: line.indent === topIndentLevel })
).join("").trim();
if (content === "") return ""; // 空行はそのまま返す
// リストを作る
if (line.indent === topIndentLevel) return content; // トップレベルの行はインデントにしない
let result = INDENT.repeat(line.indent - topIndentLevel - 1);
if (!/^\d+\. /.test(content)) result += "- "; // 番号なしの行は-を入れる
return result + content;
};
/** Nodeを変換する
*
* @param {NodeType} node
* @param {{section?:boolean}} init
* @return {string}
*/
const convertNode = (node, init) => {
const { section = false } = init ?? {};
switch (node.type) {
case "quote":
return > ${node.nodes.map((node) => convertNode(node)).join("")};
case "helpfeel":
return \`? ${node.text}\`;
case "image":
case "strongImage":
return ![image](${node.src});
case "icon":
case "strongIcon":
// 仕切り線だけ変換する
return "/icons/hr", "/scrapboxlab/hr"
.includes(node.path)
? "---"
: "";
case "strong":
return **${node.nodes.map((node) => convertNode(node)).join("")}**;
case "formula":
return $${node.formula}$;
case "decoration": {
let result = node.nodes.map((node) => convertNode(node)).join("");
if (node.decos.includes("/")) result = *${result}*;
// 見出しの変換
// お好みで変えて下さい
if (section) {
if (node.decos.includes("*-3")) result = # ${result}\n;
if (node.decos.includes("*-2")) result = ## ${result}\n;
if (node.decos.includes("*-1")) result = ### ${result}\n;
} else {
if (node.decos.some((deco) => /\*-/.test(deco0))) {
result = **${result}**;
}
}
if (node.decos.includes("~")) result = ~~${result}~~;
return result;
}
case "code":
return \`${node.text}\`;
case "commandLine":
return \`${node.symbol} ${node.text}\`;
case "link":
switch (node.pathType) {
case "root": {
const project, ...parts = node.href.split("/");
return [${node.href}](https://scrapbox.io/${project}/${encodeTitleURI(parts.join("/"))});
}
case "relative":
//@ts-ignore declare宣言が使えないため、scrapboxに型定義をつけられない
return [${node.href}](https://scrapbox.io/${scrapbox.Project.name}/${encodeTitleURI(node.href)});
default:
return node.content === "" ?
${node.href} :
[${node.content}](${node.href});
}
case "googleMap":
return [${node.place}](${node.url});
case "hashTag":
//@ts-ignore declare宣言が使えないため、scrapboxに型定義をつけられない
return [#${node.href}](https://scrapbox.io/${scrapbox.Project.name}/${node.href});
case "numberList":
return ${node.number}. ${node.nodes.map((node) => convertNode(node)).join("")};
case "blank":
case "plain":
return node.text;
}
};
HTMLに変換してコピーするUserScriptを作ってもいいかもしれない
あと$ \TeX用のも作りたい
リンクのencode用
code:convert.js
/**
* @param {string} title
* @returns {string}
*/
const encodeTitleURI = (title) => {
return ...title.map((char, index) => {
if (char === " ") return "_";
if (
!noEncodeChars.includes(char) ||
(index === title.length - 1 && noTailChars.includes(char))
) {
return encodeURIComponent(char);
}
return char;
}).join("");
};
const noEncodeChars = '@$&+=:;",';
const noTailChars = ':;",';
コードブロックの言語識別用data
code:convert.js
const extensionData = [
{
extensions: "javascript", "js",
fileType: "javascript",
},
{
extensions: "typescript", "ts",
fileType: "typescript",
},
{
extensions: "cpp", "hpp",
fileType: "cpp",
},
{
extensions: "c", "cc", "h",
fileType: "c",
},
{
extensions: "cs", "csharp",
fileType: "cs",
},
{
extensions: "markdown", "md",
fileType: "markdown",
},
{
extensions: "htm", "html",
fileType: "html",
},
{
extensions: "json",
fileType: "json",
},
{
extensions: "xml",
fileType: "xml",
},
{
extensions: "yaml", "yml",
fileType: "yaml",
},
{
extensions: "toml",
fileType: "toml",
},
{
extensions: "ini",
fileType: "ini",
},
{
extensions: "tex", "sty",
fileType: "tex",
},
{
extensions: "svg",
fileType: "svg",
},
];
コードブロックのファイル名から、programming言語を識別して返す
code:convert.js
/** ファイル名の拡張子から言語を取得する
*
* @param {string} filename
* @return {string}
*/
const getFileType = (filename) => {
const filenameExtention = filename.replace(/^.*\.(\w+)$/, "$1");
return extensionData
.find((data) => data.extensions.includes(filenameExtention))?.fileType ??
"";
};
/icons/hr.icon
前にも同じこと書いていたみたい
選択範囲をmarkdown記法に変換してclip boardにcopyするPopupMenuを作りたい
実装
1行ごとに変換
正規表現で構文解析する
UserScriptで使えるscrapbox-parserを使う
markdown記法にないものは無視する
indentの調節の仕方でoptionを作る
1. indentを維持
2. 深さに合わせてindentを相対的に減らす
#2024-12-12 20:08:00 空白を含む内部リンクが壊れないようにする
#2024-11-13 16:54:43 型定義を通す
#2024-05-27 15:41:03 numberListに対応
#2023-02-06 21:47:21
#2022-09-25 15:57:54
#2022-05-20 17:50:43
#2022-05-15 17:50:48
#2022-05-07 23:06:02
#2020-11-12 01:45:43
#2020-11-05 09:52:44
#2020-10-22 00:55:51
#2020-10-21 12:03:07
#2020-10-16 08:58:33
#2020-10-15 13:59:49
#2020-10-14 23:23:32
#2020-10-11 21:39:31
#2020-10-05 20:59:02